package polylegs;

import exceptions.IncoherentStateException;
import genome.Genome;

import gui_interface.Config;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import javax.imageio.ImageIO;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

public class Environnement {

	// input
	Config config;
	Genome genome;

	// field
	Node[][] network;//
	LinkedList<Node> possible_places = new LinkedList<Node>();// possible spots
																// for the next
																// node
	LinkedList<Node> nodes_with_incorrect_gradient = new LinkedList<Node>();
	LinkedList<Node> nodes_with_incorrect_subgradient = new LinkedList<Node>();
	// LinkedList<Node> future_nodes_with_incorrect_gradients = new
	// LinkedList<Node>();// nodes with a gradient that wil be adjusted at teh
	// next step
	LinkedList<Node> list_of_nodes = new LinkedList<Node>();// real nodes
															// present in the
															// network (no
															// rocks, not empty)

	// output
	// File movie; // juste les consignes pour peindre le resultat
	// File actions; //toutes les consignes
	boolean log = true; // true si on loggue les actions
	org.jdom2.Document document; // fichier de logs
	Element racine;
	Element currentNode;
	String actions;
	String png; // dessin final

	// FileWriter wdessin ;
	// FileWriter wactions;

	public Environnement(Config config, Genome genome, String dessin,
			String actions, String png) throws IOException {

		this.config = config;
		this.genome = genome;

		setupNetwork();

		this.png = png;
		this.actions = actions;
		if (log) {
			racine = new Element("actions");
			document = new Document(racine);
		}

		addRocks();

		Node n = network[config.x_init][config.y_init];
		possible_places.addFirst(n);

	}

	private void setupNetwork() {
		int size_environment = config.size_environment;

		network = new Node[size_environment][size_environment];
		for (int i = 0; i < size_environment; i++) {
			for (int j = 0; j < size_environment; j++) {
				network[i][j] = new Node(i, j, this);
			}
		}
		for (int i = 0; i < size_environment; i++) {
			for (int j = 0; j < size_environment; j++) {
				network[i][j].topN = network[i][(j + 1) % size_environment];
				network[i][j].botN = network[i][(j - 1 + size_environment)
						% size_environment];
				network[i][j].rightN = network[(i + 1) % size_environment][j];
				network[i][j].leftN = network[(i - 1 + size_environment)
						% size_environment][j];
			}
		}

	}

	public void addRocks() {
		// fill this as you want
		addRock(0, 0);
	}

	// form the shape using the current rules
	public void create_shape() throws IOException, IncoherentStateException {
		// tant qu'on peut ajouter des noeuds, on ajoute un noeud à un endroit
		// possible,
		// on diffuse le gradient,
		// et on ouvre/ferme les ports en accord avec les regles
		apply_rules();
		boolean wasEmptylastTime = false;
		while (list_of_nodes.size() < config.maximal_number_of_nodes
				&& !(possible_places.isEmpty()&&wasEmptylastTime)) {

			if (config.snapshotsTimes.contains(list_of_nodes.size()) & log) {
				saveImage("" + list_of_nodes.size());
			}
			if(!possible_places.isEmpty()){
				addNode();
				wasEmptylastTime = false;
			}
			else{
				wasEmptylastTime = true;
			}
			diffuse_gradient();
			diffuse_subgradient();
			apply_rules();

		}
		// we end using those because the "apply rules" can make the gradients
		// incoherent(if color change)
		// diffuse_gradient();
		// diffuse_subgradient();
		if (log) {
			saveImage("end");
			try {
				XMLOutputter sortie = new XMLOutputter(Format.getPrettyFormat());
				sortie.output(document, new FileOutputStream(actions));
			} catch (java.io.IOException e) {
				e.printStackTrace();
			}
		}

	}

	private void diffuse_gradient() throws IOException,
			IncoherentStateException {
		int number_of_gradient_updates = 0;
		while (!nodes_with_incorrect_gradient.isEmpty()) {
			LinkedList<Node> future_nodes_with_incorrect_gradients = new LinkedList<Node>();
			if (number_of_gradient_updates > 2 * config.maximal_number_of_nodes) {
				throw new IncoherentStateException();
			}
			for (Node n : nodes_with_incorrect_gradient) {
				// on accorde le gradient à celui de ses voisins,
				// et si il y a eu besoin de se mettre à jour,
				// on demande au voisin de se mettre aussi à jour

				if (log) {
					Element study = new Element("StudyGradient");
					study.setAttribute("x", "" + n.x);
					study.setAttribute("y", "" + n.y);
					currentNode.addContent(study);
				}
				if (n.topN.isNode() & n.topG != 1 + n.topN.topG) {
					if (log) {
						Element study = new Element("ChangeGradient");
						study.setAttribute("x", "" + n.x);
						study.setAttribute("y", "" + n.y);
						study.setAttribute("dir", "top");
						study.setAttribute("old", "" + n.topG);
						study.setAttribute("new", "" + (1 + n.topN.topG));
						currentNode.addContent(study);
					}

					n.topG = 1 + n.topN.topG;
					future_nodes_with_incorrect_gradients.add(n.topN);

				}
				if (n.botN.isNode() & n.botG != 1 + n.botN.botG) {

					if (log) {
						Element study = new Element("ChangeGradient");
						study.setAttribute("x", "" + n.x);
						study.setAttribute("y", "" + n.y);
						study.setAttribute("dir", "bot");
						study.setAttribute("old", "" + n.botG);
						study.setAttribute("new", "" + (1 + n.botN.botG));
						currentNode.addContent(study);
					}
					n.botG = 1 + n.botN.botG;
					future_nodes_with_incorrect_gradients.add(n.botN);
				}
				if (n.rightN.isNode() & n.rightG != 1 + n.rightN.rightG) {

					if (log) {
						Element study = new Element("ChangeGradient");
						study.setAttribute("x", "" + n.x);
						study.setAttribute("y", "" + n.y);
						study.setAttribute("dir", "right");
						study.setAttribute("old", "" + n.rightG);
						study.setAttribute("new", "" + (1 + n.rightN.rightG));
						currentNode.addContent(study);
					}
					n.rightG = 1 + n.rightN.rightG;
					future_nodes_with_incorrect_gradients.add(n.rightN);
				}
				if (n.leftN.isNode() & n.leftG != 1 + n.leftN.leftG) {
					if (log) {
						Element study = new Element("ChangeGradient");
						study.setAttribute("x", "" + n.x);
						study.setAttribute("y", "" + n.y);
						study.setAttribute("dir", "left");
						study.setAttribute("old", "" + n.leftG);
						study.setAttribute("new", "" + (1 + n.leftN.leftG));
						currentNode.addContent(study);
					}

					n.leftG = 1 + n.leftN.leftG;
					future_nodes_with_incorrect_gradients.add(n.leftN);

				}
				if (n.rightN.isNode() & n.rightN.leftG != 1 + n.leftG) {
					future_nodes_with_incorrect_gradients.add(n.rightN);
				}
				if (n.leftN.isNode() & n.leftN.rightG != 1 + n.rightG) {
					future_nodes_with_incorrect_gradients.add(n.leftN);
				}
				if (n.topN.isNode() & n.topN.botG != 1 + n.botG) {
					future_nodes_with_incorrect_gradients.add(n.topN);
				}
				if (n.botN.isNode() & n.botN.topG != 1 + n.topG) {
					future_nodes_with_incorrect_gradients.add(n.botN);
				}

			}
			nodes_with_incorrect_gradient = future_nodes_with_incorrect_gradients;

		}

		nodes_with_incorrect_gradient = new LinkedList<Node>();
		number_of_gradient_updates++;
	}

	private void diffuse_subgradient() throws IOException,
			IncoherentStateException {
		int number_of_subgradient_updates = 0;
		while (!nodes_with_incorrect_subgradient.isEmpty()) {
			// if it is impossible to have a coherent state, we throw an
			// exception
			if (number_of_subgradient_updates > 2 * config.maximal_number_of_nodes) {
				throw new IncoherentStateException();
			}

			LinkedList<Node> future_nodes_with_incorrect_subgradients = new LinkedList<Node>();
			for (Node n : nodes_with_incorrect_subgradient) {
				// on accorde le gradient à celui de ses voisins,
				// et si il y a eu besoin de se mettre à jour,
				// on demande au voisin de se mettre aussi à jour
				// wactions.write("Etude Gradient x="+n.x+" y="+n.y+"\n");
				if (log) {
					Element study = new Element("StudySubGradient");
					study.setAttribute("x", "" + n.x);
					study.setAttribute("y", "" + n.y);
					currentNode.addContent(study);
				}
				// we change the gradient of teh studied node according to our
				// neighbours
				if (n.topN.isNode() & n.color == n.topN.color
						& n.topSubG != 1 + n.topN.topSubG) {
					changeSubGradient(n.x, n.y, "top", n.topSubG,
							1 + n.topN.topSubG);
					future_nodes_with_incorrect_subgradients.add(n.topN);

				}
				if (n.botN.isNode() & n.color == n.botN.color
						& n.botSubG != 1 + n.botN.botSubG) {
					changeSubGradient(n.x, n.y, "bot", n.botSubG,
							1 + n.botN.botSubG);
					future_nodes_with_incorrect_subgradients.add(n.botN);
				}

				if (n.rightN.isNode() & n.color == n.rightN.color
						& n.rightSubG != 1 + n.rightN.rightSubG) {
					changeSubGradient(n.x, n.y, "right", n.rightSubG,
							1 + n.rightN.rightSubG);
					future_nodes_with_incorrect_subgradients.add(n.rightN);
				}
				if (n.leftN.isNode() & n.color == n.leftN.color
						& n.leftSubG != 1 + n.leftN.leftSubG) {
					changeSubGradient(n.x, n.y, "left", n.leftSubG,
							1 + n.leftN.leftSubG);
					future_nodes_with_incorrect_subgradients.add(n.leftN);

				}
				// if teh neighbours are not coherent, we add them to teh list
				// of incorrect subgradients
				if (n.rightN.isNode() & n.color == n.rightN.color
						& n.rightN.leftSubG != 1 + n.leftSubG) {
					future_nodes_with_incorrect_subgradients.add(n.rightN);
				}
				if (n.leftN.isNode() & n.color == n.leftN.color
						& n.leftN.rightSubG != 1 + n.rightSubG) {
					future_nodes_with_incorrect_subgradients.add(n.leftN);
				}
				if (n.topN.isNode() & n.color == n.topN.color
						& n.topN.botSubG != 1 + n.botSubG) {
					future_nodes_with_incorrect_subgradients.add(n.topN);
				}
				if (n.botN.isNode() & n.color == n.botN.color
						& n.botN.topSubG != 1 + n.topSubG) {
					future_nodes_with_incorrect_subgradients.add(n.botN);
				}

			}
			nodes_with_incorrect_subgradient = future_nodes_with_incorrect_subgradients;

		}

		nodes_with_incorrect_subgradient = new LinkedList<Node>();
		number_of_subgradient_updates++;
	}

	private void addNode() throws IOException {
		// on utilise la liste des positions possibles
		// la nouvelle places est choisie aleatoirement
		int length_list = possible_places.size();
		int random = (int) (Math.random() * length_list);

		Node n = possible_places.get(random);
		n.becomeNode();
		list_of_nodes.add(n);
		nodes_with_incorrect_gradient.add(n);
		nodes_with_incorrect_subgradient.add(n);
		if (log) {
			Element newnode = new Element("AddNode");
			newnode.setAttribute("x", "" + n.x);
			newnode.setAttribute("y", "" + n.y);
			newnode.setAttribute("topG", "" + n.topG);
			newnode.setAttribute("botG", "" + n.botG);
			newnode.setAttribute("rightG", "" + n.rightG);
			newnode.setAttribute("leftG", "" + n.leftG);
			newnode.setAttribute("color", "" + n.color);
			currentNode = newnode;
			racine.addContent(currentNode);

		}
		/*
		 * wdessin.write(n.x+" "+n.y+"\n");
		 * wactions.write("New Node : x="+n.x+" y="+n.y+ " topG="+n.topG+
		 * " botG="+n.botG+ " rightG="+n.rightG+ " leftG="+n.leftG+"\n");
		 */
		// ici on gere le gradient de ce premier neoud intégré

		possible_places.clear();

	}

	private void apply_rules() throws IOException {
		for (Node n : list_of_nodes) {
			// on remet à zero les ports
			n.topP = false;
			n.botP = false;
			n.rightP = false;
			n.leftP = false;
			// le nouveau type est le type en cours.
			n.new_color = n.color;

			// on applique les regles
			genome.applyTo(n);

			// on change le type
			if (n.new_color != n.color) {

				if (log) {
					Element port = new Element("ChangeType");
					port.setAttribute("x", "" + n.x);
					port.setAttribute("y", "" + n.y);
					port.setAttribute("old", "" + n.color);
					port.setAttribute("new", "" + n.new_color);
					currentNode.addContent(port);
				}
				n.color = n.new_color;

				// the color change means that my subgradients and the
				// subgradients of my neighbours are incorrect ,
				// we need to change that
				changeSubGradient(n, "top", 0);
				changeSubGradient(n, "bot", 0);
				changeSubGradient(n, "right", 0);
				changeSubGradient(n, "left", 0);
				nodes_with_incorrect_subgradient.add(n);

				if (n.topN.isNode() && n.color != n.topN.color) {
					changeSubGradient(n.topN, "bot", 0);
					nodes_with_incorrect_subgradient.add(n.topN);
				}
				if (n.botN.isNode() && n.color != n.botN.color) {
					changeSubGradient(n.botN, "top", 0);
					nodes_with_incorrect_subgradient.add(n.botN);
				}
				if (n.rightN.isNode() && n.color != n.rightN.color) {
					changeSubGradient(n.rightN, "left", 0);
					nodes_with_incorrect_subgradient.add(n.rightN);
				}
				if (n.leftN.isNode() && n.color != n.leftN.color) {
					changeSubGradient(n.leftN, "right", 0);
					nodes_with_incorrect_subgradient.add(n.leftN);
				}

			}

			// on ajoute les nouveaux spots possibles à la liste
			if (n.topP == true & n.topN.isEmpty()) {
				// wactions.write("Open topPort x="+n.x+" y="+n.y+"\n");
				if (log) {
					Element port = new Element("OpenPort");
					port.setAttribute("x", "" + n.x);
					port.setAttribute("y", "" + n.y);
					port.setAttribute("dir", "top");
					;
					currentNode.addContent(port);

				}
				possible_places.addFirst(n.topN);
			}
			if (n.botP == true & n.botN.isEmpty()) {
				// wactions.write("Open botPort x="+n.x+" y="+n.y+"\n");
				if (log) {
					Element port = new Element("OpenPort");
					port.setAttribute("x", "" + n.x);
					port.setAttribute("y", "" + n.y);
					port.setAttribute("dir", "bot");
					;
					currentNode.addContent(port);

				}
				possible_places.addFirst(n.botN);
			}
			if (n.rightP == true & n.rightN.isEmpty()) {
				// wactions.write("Open rightPort x="+n.x+" y="+n.y+"\n");
				if (log) {
					Element port = new Element("OpenPort");
					port.setAttribute("x", "" + n.x);
					port.setAttribute("y", "" + n.y);
					port.setAttribute("dir", "right");
					;
					currentNode.addContent(port);

				}
				possible_places.addFirst(n.rightN);
			}
			if (n.leftP == true & n.leftN.isEmpty()) {
				if (log) {
					Element port = new Element("OpenPort");
					port.setAttribute("x", "" + n.x);
					port.setAttribute("y", "" + n.y);
					port.setAttribute("dir", "left");
					;
					currentNode.addContent(port);

				}
				// wactions.write("Open leftPort x="+n.x+" y="+n.y+"\n");
				possible_places.addFirst(n.leftN);
			}

		}

	}

	public BufferedImage getImage() {
		Dimension tailleEcran = java.awt.Toolkit.getDefaultToolkit()
				.getScreenSize();
		int hauteur = (int) tailleEcran.getHeight();
		int largeur = (int) tailleEcran.getWidth();

		BufferedImage theImage = new BufferedImage(largeur, hauteur,
				BufferedImage.TYPE_INT_RGB);
		Graphics2D g2d = (Graphics2D) theImage.getGraphics();
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		int size_environment = config.size_environment;
		int largeur_rect = largeur / size_environment;
		int hauteur_rect = hauteur / size_environment;

		for (int i = 0; i < size_environment; i++) {
			for (int j = 0; j < size_environment; j++) {
				switch (network[i][j].type) {
				case EMPTY:
					g2d.setColor(Color.BLACK);
					break;
				case NODE:
					g2d.setColor(Color.YELLOW);
					break;
				case ROCK:
					g2d.setColor(Color.GRAY);
					break;

				}

				g2d.fillRect(i * largeur_rect, hauteur - j * hauteur_rect,
						largeur_rect, hauteur_rect);

			}
		}
		return theImage;
	}

	private void saveImage(String s) {
		try {
			ImageIO.write(getImage(), "PNG",
					new File(png.replace(".png", "_" + s + ".png")));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public Node getNode(int x, int y) {
		return network[x][y];
	}

	private void changeSubGradient(int x, int y, String dir, int old, int new1) {
		if (log) {
			Element study = new Element("ChangeSubGradient");
			study.setAttribute("x", "" + x);
			study.setAttribute("y", "" + y);
			study.setAttribute("dir", dir);
			study.setAttribute("old", "" + old);
			study.setAttribute("new", "" + new1);
			currentNode.addContent(study);
		}
		if (dir.equals("top")) {
			getNode(x, y).topSubG = new1;
		}
		if (dir.equals("bot")) {
			getNode(x, y).botSubG = new1;
		}
		if (dir.equals("right")) {
			getNode(x, y).rightSubG = new1;
		}
		if (dir.equals("left")) {
			getNode(x, y).leftSubG = new1;
		}
	}

	private void changeSubGradient(Node n, String dir, int new1) {
		if (dir.equals("top")) {
			changeSubGradient(n.x, n.y, dir, n.topSubG, new1);
		}
		if (dir.equals("bot")) {
			changeSubGradient(n.x, n.y, dir, n.botSubG, new1);
		}
		if (dir.equals("right")) {
			changeSubGradient(n.x, n.y, dir, n.rightSubG, new1);
		}
		if (dir.equals("left")) {
			changeSubGradient(n.x, n.y, dir, n.leftSubG, new1);
		}
	}

	private void addRock(int x, int y) {
		network[x][y].becomeRock();
		if (log) {
			Element newnode = new Element("AddRock");
			newnode.setAttribute("x", "" + x);
			newnode.setAttribute("y", "" + y);
			racine.addContent(newnode);
		}
	}
}
